/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 * 表示一个参数的元数据信息。
 */
public final class ParameterMeta {
	private String identifier;
	private Class<?> dataType;
	private Class<?> itemType;
	private int position;
	private ParameterDirection direction;
	private ParameterRole role;
	private int maxLength;
	private boolean fixedLength;
	private boolean ascii;
	private String camelIdentifier;
	private String lowerIdentifier;
	private boolean _isPrimitive;
	private Integer sqlType;

	// 基础类型列表，用于判断一个类型是否是基础类型
	private static final Set<Class<?>> PRIMITIVE_TYPES = getPrimitiveTypes();

	// 获取基础类型的参数列表
	private static Set<Class<?>> getPrimitiveTypes() {
		Set<Class<?>> types = new HashSet<Class<?>>();
		
		types.add(Boolean.class);
		types.add(byte[].class);
		types.add(LocalDate.class);
		types.add(LocalDateTime.class);
		types.add(BigDecimal.class);
		types.add(Double.class);
		types.add(Float.class);
		types.add(Integer.class);
		types.add(Long.class);
		types.add(Short.class);
		types.add(Byte.class);
		types.add(String.class);
		types.add(LocalTime.class);
		types.add(UUID.class);
		
		return types;
	}

	/**
	 * 使用参数的标记信息和反射信息初始化 ParameterMeta 类的新实例。
	 * 
	 * @param annotation
	 *            标记信息。
	 * @param field
	 *            反射信息。
	 * @throws IllegalArgumentException
	 *             annotation 或 field 为 null。
	 */
	public ParameterMeta(ParameterAnnotation annotation, Field field) {
		if (annotation == null) {
			throw new IllegalArgumentException("annotation 为 null。");
		}

		if (field == null) {
			throw new IllegalArgumentException("field 为 null。");
		}

		this.identifier = field.getName();
		this.dataType = field.getType();
		if (DataList.class.isAssignableFrom(this.dataType)) {
			ParameterizedType gt = (ParameterizedType) field.getGenericType();
			this.itemType = (Class<?>) gt.getActualTypeArguments()[0];
		} else {
			this.itemType = null;
		}
		this.position = annotation.position();
		this.direction = annotation.direction();
		this.role = annotation.role();
		this.maxLength = annotation.maxLength();
		this.fixedLength = annotation.fixedLength();
		this.ascii = annotation.ascii();
		this.camelIdentifier = IdentifierHelper.camel(this.identifier);
		this.lowerIdentifier = this.identifier.toLowerCase();
		this._isPrimitive = PRIMITIVE_TYPES.contains(this.dataType);
		this.sqlType = null;
	}

	/**
	 * 获取一个值，该值指示参数类型是否基础类型。
	 * 
	 * @return 指示参数类型是否基础类型。
	 */
	public boolean isPrimitive() {
		return _isPrimitive;
	}

	/**
	 * 获取参数的标识符。
	 * 
	 * @return 参数的标识符。
	 */
	public String getIdentifier() {
		return identifier;
	}

	/**
	 * 获取参数的数据类型。
	 * 
	 * @return 参数的数据类型。
	 */
	public Class<?> getDataType() {
		return dataType;
	}

	/**
	 * 当参数类型是 DataList 时，获取集合元素的类型。
	 * 
	 * @return DataList 中元素的类型，如果不是 DataList，则返回 null。
	 */
	public Class<?> getItemType() {
		return this.itemType;
	}

	/**
	 * 获取参数的数据库类型代码。如果尚未初始化返回 null。
	 * 
	 * @return 参数的数据库类型代码。
	 */
	public Integer getSqlType() {
		return this.sqlType;
	}

	/**
	 * 设置参数的数据库类型代码。
	 * 在一个应用生命运行周期中，目标功能使用的数据库类型是固定的，因此在第一次获得参数的数据库类型代码后通过该方法缓存类型代码，以避免每次都要通过对比
	 * dataType 来获取参数的数据库类型代码。
	 * 
	 * @param value
	 *            参数的数据库类型代码。
	 */
	public void setSqlType(Integer value) {
		this.sqlType = value;
	}

	/**
	 * 获取参数在参数列表中的位置，从 0 开始。
	 * 
	 * @return 参数在参数列表中的位置，从 0 开始。
	 */
	public int getPosition() {
		return position;
	}

	/**
	 * 获取参数的方向，默认为 In。
	 * 
	 * @return 参数的方向。
	 */
	public ParameterDirection getDirection() {
		return direction;
	}

	/**
	 * 获取一个值，该值指示参数是否是输入参数（In 或 InOut）。
	 * 
	 * @return 如果参数方向为 In 或 InOut，则返回 true，否则返回 false。
	 */
	public boolean isIn() {
		return this.direction == ParameterDirection.IN || this.direction == ParameterDirection.IN_OUT;
	}

	/**
	 * 获取一个值，该值指示参数是否是输出参数（Out 或 InOut）。
	 * 
	 * @return 如果参数方向为 Out 或 InOut，则返回 true，否则返回 false。
	 */
	public boolean isOut() {
		return this.direction == ParameterDirection.OUT || this.direction == ParameterDirection.IN_OUT;
	}

	/**
	 * 获取参数的作用。默认为 Normal。
	 * 
	 * @return 参数的作用。
	 */
	public ParameterRole getRole() {
		return role;
	}

	/**
	 * 获取字符串或二进制流参数的最大长度，或者十进制数字的小数位数。默认为 0。
	 * 
	 * @return 字符串或二进制流参数的最大长度，或者十进制数字的小数位数。
	 */
	public int getMaxLength() {
		return maxLength;
	}

	/**
	 * 获取一个值，该值指示字符串或二进制流的长度是固定值。默认为 false。
	 * 
	 * @return 指示字符串或二进制流的长度是固定值。
	 */
	public boolean isFixedLength() {
		return fixedLength;
	}

	/**
	 * 获取一个值，该值指示字符串是否仅仅包含 ASCII 字符。默认为 false。
	 * 
	 * @return 指示字符串是否仅仅包含 ASCII 字符。
	 */
	public boolean isAscii() {
		return ascii;
	}

	/**
	 * 获取参数标识符的 camel 格式。
	 * 
	 * @return 参数标识符的 camel 格式。
	 */
	public String getCamelIdentifier() {
		return camelIdentifier;
	}

	/**
	 * 获取参数标识符的小写格式。
	 * 
	 * @return 参数标识符的小写格式。
	 */
	public String getLowerIdentifier() {
		return lowerIdentifier;
	}
	
	/**
	 * 将指定的字符串的值转换为当前参数类型表示的类型的值。
	 * 
	 * @param s
	 *            字符串值。
	 * @return 类型为 dataType 的值。
	 */
	public Object valueFromString(String s) {
		// 目标类型是 String，原样返回
		if (this.dataType == String.class) {
			return s;
		}
		
		// 对于其他值，如果没有提供字符串（字符串是 null 或空字符串），则总是认为 null
		if (s == null || s.length() == 0) {
			return null;
		}
		
		// 目标类型是布尔值
		if (this.dataType == Boolean.class) {
			return pittypat.primitives.Boolean.parse(s);
		}

		// 目标类型是 UUID
		if (this.dataType == UUID.class) {
			return pittypat.primitives.Uuid.parse(s);
		}

		// 目标类型是字节数组，总是认为字符串是 base64 格式
		if (this.dataType == byte[].class) {
			return pittypat.primitives.Binary.parse(s);
		}

		// 目标类型是 8 位整数
		if (this.dataType == Byte.class) {
			return pittypat.primitives.Byte.parse(s);
		}

		// 目标类型是 16 位整数
		if (this.dataType == Short.class) {
			return pittypat.primitives.Short.parse(s);
		}

		// 目标类型是 32 位整数
		if (this.dataType == Integer.class) {
			return pittypat.primitives.Int.parse(s);
		}

		// 目标类型是 64 位整数
		if (this.dataType == Long.class) {
			return pittypat.primitives.Long.parse(s);
		}

		// 目标类型是精确的十进制数字
		if (this.dataType == BigDecimal.class) {
			return pittypat.primitives.Decimal.parse(s);
		}

		// 目标类型是 32 位浮点数
		if (this.dataType == Float.class) {
			return pittypat.primitives.Float.parse(s);
		}

		// 目标类型是 64 位浮点数
		if (this.dataType == Double.class) {
			return pittypat.primitives.Double.parse(s);
		}

		// 目标类型是日期时间值
		if (this.dataType == LocalDateTime.class) {
			return pittypat.primitives.DateTime.parse(s);
		}

		// 目标类型是日期值
		if (this.dataType == LocalDate.class) {
			return pittypat.primitives.Date.parse(s);
		}

		// 目标类型是时间值
		if (this.dataType == LocalTime.class) {
			return pittypat.primitives.Time.parse(s);
		}

		// 对于其他不支持的类型，返回原始字符串
		return s;
	}
}
